package com.linran.wxpublic.service;

import com.github.rholder.retry.*;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;

/**
 * 重试机制
 * 可以设置任务单次执行的时间限制，如果超时则抛出异常。
 * 可以设置重试监听器，用来执行额外的处理工作。
 * 可以设置任务阻塞策略，即可以设置当前重试完成，下次重试开始前的这段时间做什么事情。
 * 可以通过停止重试策略和等待策略结合使用来设置更加灵活的策略，比如指数等待时长并最多10次调用，随机等待时长并永不停止等等。
 *
 * 线程安全问题:
 * 如上代码2我们创建了一个需要重试的任务，代码3添加需要重试方法到执行器。最后Retryer是线程安全的？也就是整个JVM内我创建了一个retryer实例，多线程同时向retryer里面添加重试任务，会存在问题？
 * 答案是，不存在问题，其是线程安全的。这是因为Retryer内部与重试相关的变量是在第一次添加任务时候，通过new了与任务相关的ResultAttempt进行隔离了，每个重试任务有自己的ResultAttempt变量来记录重试信息。
 *
 * WaitStrategies.fixedWait(2, TimeUnit.SECONDS) 等待固定多少时间进行重试
 * WaitStrategies.incrementingWait(3, TimeUnit.SECONDS,1,TimeUnit.SECONDS) 每次重试等待时间添加个增量
 * WaitStrategies.fibonacciWait(1000,10,TimeUnit.SECONDS) 斐波那契数列方式设置重试间隔。
 */
@Service
public class RetryService {

    /**
     * alwaysTrue() 无论返回值是啥，都进行重试
     * alwaysFalse() 返回值是啥，都不进行重试
     * isNull() 返回值为null，进行重试
     * notNull() 返回值不为null，进行重试
     * not(Predicate predicate) 自己设置谓词表达式，决定返回值为啥时候时候进行重试
     */
    @Bean
    public Retryer<String> retryer() {
        return RetryerBuilder.<String>newBuilder()
                //可以自定义异常
//                .retryIfResult(Predicates.<Boolean>isNull())// 1.1当重试的方法返回null时候进行重试
                .retryIfExceptionOfType(RuntimeException.class)
                .retryIfResult(StringUtils::isEmpty)
                //实际使用中，间隔时间3~5分钟
                .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))
//                .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(3, TimeUnit.SECONDS)) //因为方法不存在的问题导致方法调用失败，可能jar冲突
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                .build();
    }

    public void retryCall() {
        try {
            String call = retryer().call(()->remoteMethod());
            System.out.printf("call result:%s%n", call);
            //业务处理
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String remoteMethod() {
        System.out.println("被调用~!");
//        int i = 3 / 0;
//        return "";
//        try {
//            TimeUnit.SECONDS.sleep(5);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        return "test";
    }
}
